package jsr292.java.lang.invoke;

import jsr292.weaver.Debug;
import jsr292.weaver.opt.OptimizerContext;
import jsr292.weaver.opt.OptimizerSnippets;
import jsr292.weaver.opt.OptimizerVarAllocator;

class MHSpreader extends MethodHandle {
    private final MethodHandle mh;
    private final int arrayLength;
    

    public MHSpreader(MethodHandle mh, int arrayLength, MethodType type) {
        super(type);
        this.mh = mh;
        this.arrayLength = arrayLength;
    }
    
    @Override
    public Object $invoke$(Object o1) throws Throwable {
	int length = arrayLength;
	if (length == 0) {
	    return mh.$invoke$();
	}
	
        Object[] array=(Object[])o1;
        switch (length) {
        case 1:
            return mh.$invoke$(array[0]);
        case 2:
            return mh.$invoke$(array[0], array[1]);
        case 3:
            return mh.$invoke$(array[0], array[1], array[2]);
        case 4:
            return mh.$invoke$(array[0], array[1], array[2], array[3]);
        case 5:
            return mh.$invoke$(array[0], array[1], array[2], array[3], array[4]);
        case 6:
            return mh.$invoke$(array[0], array[1], array[2], array[3], array[4], array[5]);
        case 7:
            return mh.$invoke$(array[0], array[1], array[2], array[3], array[4], array[5], array[6]);
        }
        return mh.$invoke$(array);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2) throws Throwable {
	int length = arrayLength;
	if (length == 0) {
	    return mh.$invoke$(o1);
	}
	
	Object[] array=(Object[])o2;
        switch (length) {
        case 1:
            return mh.$invoke$(o1, array[0]);
        case 2:
            return mh.$invoke$(o1, array[0], array[1]);
        case 3:
            return mh.$invoke$(o1, array[0], array[1], array[2]);
        case 4:
            return mh.$invoke$(o1, array[0], array[1], array[2], array[3]);
        case 5:
            return mh.$invoke$(o1, array[0], array[1], array[2], array[3], array[4]);
        case 6:
            return mh.$invoke$(o1, array[0], array[1], array[2], array[3], array[4], array[5]);
        }
        
        Object[] rest = new Object[1 + length];
        System.arraycopy(array, 0, rest, 1, length);
        rest[0] = o1;
        return mh.$invoke$(rest);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3) throws Throwable {
	int length = arrayLength;
	if (length == 0) {
	    return mh.$invoke$(o1, o2);
	}
	
        Object[] array=(Object[])o3;
        switch (length) {
        case 1:
            return mh.$invoke$(o1, o2, array[0]);
        case 2:
            return mh.$invoke$(o1, o2, array[0], array[1]);
        case 3:
            return mh.$invoke$(o1, o2, array[0], array[1], array[2]);
        case 4:
            return mh.$invoke$(o1, o2, array[0], array[1], array[2], array[3]);
        case 5:
            return mh.$invoke$(o1, o2, array[0], array[1], array[2], array[3], array[4]);
        }
        
        Object[] rest = new Object[2 + length];
        System.arraycopy(array, 0, rest, 2, length);
        rest[0] = o1;
        rest[1] = o2;
        return mh.$invoke$(rest);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4) throws Throwable {
	int length = arrayLength;
	if (length == 0) {
	    return mh.$invoke$(o1, o2, o3);
	}
	
	Object[] array=(Object[])o4;
        switch (length) {
        case 1:
            return mh.$invoke$(o1, o2, o3, array[0]);
        case 2:
            return mh.$invoke$(o1, o2, o3, array[0], array[1]);
        case 3:
            return mh.$invoke$(o1, o2, o3, array[0], array[1], array[2]);
        case 4:
            return mh.$invoke$(o1, o2, o3, array[0], array[1], array[2], array[3]);
        }
        
        Object[] rest = new Object[3 + length];
        System.arraycopy(array, 0, rest, 3, length);
        rest[0] = o1;
        rest[1] = o2;
        rest[2] = o3;
        return mh.$invoke$(rest);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5) throws Throwable {
	int length = arrayLength;
	if (length == 0) {
	    return mh.$invoke$(o1, o2, o3, o4);
	}
	
	Object[] array=(Object[])o5;
        switch (length) {
        case 1:
            return mh.$invoke$(o1, o2, o3, o4, array[0]);
        case 2:
            return mh.$invoke$(o1, o2, o3, o4, array[0], array[1]);
        case 3:
            return mh.$invoke$(o1, o2, o3, o4, array[0], array[1], array[2]);
        }
        
        Object[] rest = new Object[4 + length];
        System.arraycopy(array, 0, rest, 4, length);
        rest[0] = o1;
        rest[1] = o2;
        rest[2] = o3;
        rest[3] = o4;
        return mh.$invoke$(rest);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6) throws Throwable {
	int length = arrayLength;
	if (length == 0) {
	    return mh.$invoke$(o1, o2, o3, o4, o5);
	}
	
	Object[] array=(Object[])o6;
        switch (length) {
        case 1:
            return mh.$invoke$(o1, o2, o3, o4, o5, array[0]);
        case 2:
            return mh.$invoke$(o1, o2, o3, o4, o5, array[0], array[1]);
        }

        Object[] rest = new Object[5 + length];
        System.arraycopy(array, 0, rest, 5, length);
        rest[0] = o1;
        rest[1] = o2;
        rest[2] = o3;
        rest[3] = o4;
        rest[4] = o5;
        return mh.$invoke$(rest);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7) throws Throwable {
	int length = arrayLength;
	if (length == 0) {
	    return mh.$invoke$(o1, o2, o3, o4, o5, o6);
	}
	
	Object[] array=(Object[])o7;
        if (length == 1) {
            return mh.$invoke$(o1, o2, o3, o4, o5, o6, array[0]);
        }
        
        Object[] rest = new Object[6 + length];
        System.arraycopy(array, 0, rest, 6, length);
        rest[0] = o1;
        rest[1] = o2;
        rest[2] = o3;
        rest[3] = o4;
        rest[4] = o5;
        rest[5] = o6;
        return mh.$invoke$(rest);
    }
    
    @Override
    public Object $invoke$(Object... rest) throws Throwable {
	int arrayLength = this.arrayLength;
	int uncollectedLength = rest.length - 1;
	if (arrayLength == 0) {
	    rest = ArrayUtils.copyOf(rest, uncollectedLength);
	    return mh.invokeWithArguments(rest);   
	}
	
	Object[] array = (Object[])rest[uncollectedLength];
	rest = ArrayUtils.copyOf(rest, uncollectedLength + arrayLength);
	System.arraycopy(array, 0, rest, uncollectedLength, arrayLength);
	
        return mh.invokeWithArguments(rest); 
    }
    
    @Override
    public boolean isWeavable(Class<?> classBeingRedefined) {
        return mh.isWeavable(classBeingRedefined);
    }
    @Override
    public void weave(OptimizerContext context, OptimizerVarAllocator allocator) {
        if (Debug.DEBUG) {
            Debug.checkAllocator(allocator, this);
        }
        
        OptimizerSnippets.spread(context, allocator, arrayLength, methodType, mh);
    }
}
